package mymyoux.utils
{
	import flash.utils.Dictionary;
	
	import mymyoux.core.IObject;
	import mymyoux.events.HashEvent;
	import mymyoux.events.MEventDispatcher;
		 /**
         * Hash dispatche l'event update
         * quand une donnée est modifiée ou crée.
         * @eventType mymyoux.events.HashEvent
         */
        [Event(name="update", type="mymyoux.events.HashEvent")]
		 /**
         * Hash dispatche l'event remove
         * quand une donnée est supprimée.
         * @eventType mymyoux.events.HashEvent
         */
        [Event(name="remove", type="mymyoux.events.HashEvent")]
     /**
	 * Hash permet de stocker des données sous la forme clé=valeur. 
	 * Il est également possible de rajouter des écouteurs pour être au courant des ajouts/suppressions de données.
	 * @author Mymyoux
	 * @version 2.0
	 */
	public dynamic class Hash extends MEventDispatcher implements IMap
	{
		/**
		 * Dictionary stockant les données.
		 */
		protected var dictionary:Dictionary;
		/**
		 * @private
		 */
		protected static var __idUnique:Number=0;
		/**
		 * @private
		 */
		protected var _idUnique:Number;
		/**
		 * Crée un objet de type Hash.
		 * @param weakKeys Crée un lien fort ou pas.
		 */
		public function Hash(weakKeys:Boolean=false){
			_idUnique=__idUnique++;
			dictionary=new Dictionary(weakKeys);
		}
		/**
		 * Indique si les 2 Hashs sont identiques.
		 * La fonction teste en réalité si ceux-sont 2 références au même Hash ou non.
		 * Si ceux-sont 2 Référence à 2 Hashs différents mais contenant les mêmes valeurs, la fonction retournera false.
		 * @param hash Hash dont on souhaite savoir s'il est identique ou non à l'instance en cours.
		 * @return true si ceux sont les 2 même hash, false sinon.
		 */
		 public override function equals(objet:IObject):Boolean{
		 	try{
			 	var hash:Hash=objet as Hash;
			 	return hash.idUnique==idUnique;
		 	}catch(e:Error){
		 		return false;
		 	}
		 	return false;
		 }
		 /**
		 * Id unique associée au Hash.
		 * Permet que la fonction equals fonctionne.
		 * @see #equals()
		 */
		 public function get idUnique():Number{
		 	return _idUnique;
		 }
		/**
		 * Enregistre une donnée.
		 * Dispatche l'évènement mymyoux.events.HashEvent.UPDATE si la valeur a été modifée ou crée.
		 * Si key ou value sont nuls alors aucune action n'est effectuée.
		 * @param key Clé avec laquelle est liée la donnée.
		 * @param value Valeur de la donnée.
		 * @see mymyoux.events.HashEvent#UPDATE
		 */
		public function put(key:Object,value:Object):void{
			if(key!=null && value!=null){
				if(containsKey(key)){
					try{
						if(dictionary[key].equals(value)){
							return;
						}
					}catch(e:Error){
						if(dictionary[key]===value){
							return;
						}					
					}
				}
				dictionary[key]=value;
				dispatchEvent(new HashEvent(HashEvent.UPDATE,key,value));
			}
		}
		/**
		 * Enregistre un jeu de données.
		 * @param map Structure de type IMap contenant les données à enregistrer.
		 */
		public function putAll(map:IMap):void{
			if(map!=null){
				var r:Array=map.getKeys();
				for each(var i:String in r){
					//dictionary[i]=map.get(i);
					put(i,map.get(i));//permet le dispatch d'évènements
				}
			}
		}
		/**
		 * Supprime une donnée à partir de sa clé.
		 * @param key Clé à laquelle la donnée à supprimer est associée.
		 * Si la clé n'existe pas, aucun changement n'est effectué.
		 * Dispatche l'évènement mymyoux.events.HashEvent.REMOVE si la donnée existait.
		 * @see mymyoux.events.HashEvent#REMOVE
		 */
		public function remove(key:Object):void{
			if(containsKey(key))
			{
				var value:*=get(key);
				dictionary[key]=null;
				delete dictionary[key];
				dispatchEvent(new HashEvent(HashEvent.REMOVE,key,value));
			}
		}
		/**
		 * Supprime une donnée à partir de sa valeur.
		 * Peut supprimer plusieurs fois la valeur si elle est indexée avec plusieurs clés.
		 * removeValue() teste d'abord l'existence de la donnée par la méthode equals() si elle existe ou par l'égalité stricte ===.
		 * Appel remove() pour chaque clé indexant la donnée.
		 * @param value Valeur de la donnée à supprimer.
		 * @see #remove()
		 */
		public function removeValue(value:Object):void{
			if(containsValue(value)){
				var keys:Array=getKeys();
				for each(var i:Object in keys){
					try{
						if(dictionary[i].equals(value)){
							remove(i);
							if(!containsValue(value)){
								return;
							}
						}
					}catch(e:Error){
						if(dictionary[i]===value){
							remove(i);
							if(!containsValue(value)){
								return;
							}
						}
					}
				}
			}
		}
		/**
		 * Supprime toutes les données indexées.
		 * Appel remove() pour chaque clé existante.
		 * @see #remove()
		 */
		public function clear():void{
			for each(var i:Object in getKeys()){
				remove(i);
			}
		}
		/**
		 * Indique si le Hash est vide ou non.
		 * @return true si le Hash est vide, false sinon.
		 */
		public function isEmpty():Boolean{
			for(var i:String in dictionary){
				return false;
			}
			return true;
		}
		/**
		 * Indique si la clé existe.
		 * @param key Clé à tester.
		 * @return true si la clé existe, false sinon.
		 */
		public function containsKey(key:Object):Boolean{
			if(key==null){
				return false;
			}
			return dictionary[key]!=null;
		}
		/**
		 * Indique si la donnée est indexée.
		 * @param value Donnée à tester.
		 * @return true si la donnée est indexée, false sinon.
		 */
		public function containsValue(value:Object):Boolean{
			if(value==null){
				return false;
			}
			for each(var i:Object in dictionary){
				try{
					if(i.equals(value)){
						return true;
					}
				}catch(e:Error){
					if(i===value){
						return true;
					}
				}
			}
			return false;
		}
		/**
		 * 	Retourne la donnée associée à la clé.
		 *  @param key Clé à laquelle est associée la donnée à retourner.
		 *  @return La donnée associée à la clé si la clé existe, null sinon.	
		 */
		public function get(key:Object):Object{	
			if(key==null){
				return null;
			}
			return dictionary[key];
		}
		/**
		 * @inheritDoc 
		 */
		public function getKeys(value:Object=null):Array{
			//si value != null on ne renvoie que les clé ayant la valeur indiquée
			var r:Array=new Array();
			for(var i:String in dictionary){
				if(dictionary[i]!=null){
					try{
						if(value==null || dictionary[i].equals(value)){
							r.push(i);
						}
					}catch(e:Error){
						if(value==null || dictionary[i]===value){
							r.push(i);
						}					
					}
				}
			}
			return r;
		}
		/**
		 * Retourne un Array contenant les valeurs indexées dans le Hash.
		 * @param unique Indique si les données doivent n'apparaitre qu'une seule fois dans l'Array ou peuvent apparaitre plusieurs fois.
		 * Si unique vaut true alors chaque donnée n'apparait qu'une seule fois même si elle est indexée plusieurs fois.
		 * @return Tableau des données.
		 */
		public function getValues(unique:Boolean=false):Array{
			var r:Array=new Array();
			try{
			for(var i:String in dictionary){
				try{
				if(!unique || r.indexOf(dictionary[i])==-1){
					r.push(dictionary[i]);
				}
				}catch(e:Error){
					
				}
			}
			}catch(err:Error){
				
			}
			return r;
		}
		/**
		 * Retourne un clone du Hash.
		 * Permet de faire un passage par valeur et non par référence.
		 * @return Une nouvelle instance de Hash contenant les mêmes clés/données que celui en cours.
		 */
		public function clone():Hash{
			var r:Hash=new Hash();
			r.putAll(this);
			return r;
		}
		/**
		 * Renvoie le nombre de clés existantes.
		 * @return Le nombre de clés existantes.
		 */
		public override function get length():uint{
			return getKeys().length;
		}
			/**
		 * @inheritDoc 
		 */

		public override function toString():String{
			var keys:Array=getKeys();
			var result:String="[Hash:";
			for(var i:String in keys){
				result+=keys[i]+"="+get(keys[i])+",";
			}
			result=result.substring(0,result.length-1)+"]";
			return result;
		}
		/**
		 * Retourne un XML contenant les valeurs indexées dans le Hash.
		 * @param name Nom de la balise principale du XML.
		 * @param name_item Nom de la balise de chaque élément.
		 * @param name_cle Nom de la balise clé.
		 * @param name_value Nom de la balise contenant la valeur.
		 * @return le XML.
		 */
		public function toXML(name:String="hash",name_item:String="item",name_cle:String="key",name_value:String="value"):XML
		{
			var x:XML=new XML("<"+name+"></"+name+">");
			var keys:Array=getKeys();
			for(var i:String in keys)
			{
				x.firstChild+=<{name_item}><{name_cle}>{keys[i]}</{name_cle}><{name_value}>{get(keys[i])}</{name_value}></{name_item}>;
			}
			return x;
		}
	}
}